package git.soulbgm.utils;
import git.soulbgm.pojo.ServerSoftwareStatus;
import git.soulbgm.pojo.Server;

import git.soulbgm.config.MonitorConfig;
import git.soulbgm.constant.Constants;
import git.soulbgm.pojo.*;
import git.soulbgm.socket.UdpClient;
import org.springframework.stereotype.Component;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.hardware.NetworkIF;
import oshi.software.os.OSFileStore;

import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 监控服务器工具类
 *
 * @author SoulBGM
 * @date 2020/6/29 17:07
 */
@Component
public class MonitorUtil {

    private static final LogUtil LOG = new LogUtil(MonitorUtil.class);
    /**
     * 没有合适的服务器提示信息
     */
    private static final String NO_SERVER_SUITABLE_MESSAGE = "no server suitable for synchronization found";
    /**
     * 获取IP的正则表达式
     */
    private static final Pattern IP_PATTERN = Pattern.compile("time server (.+?) offset ");
    /**
     * 获取秒数的正则表达式
     */
    private static final Pattern SEC_PATTERN = Pattern.compile("offset (.+?) sec");
    /**
     * 最新的服务各项信息
     */
    private static Server latestServer;
    /**
     * 最新时间同步信息
     */
    private static TimeSyncInfo latestNtpdateInfo;
    /**
     * NTP地址
     */
    private static String ntpIp;
    /**
     * 系统信息
     */
    private static SystemInfo systemInfo;
    /**
     * 硬件抽象层
     */
    private static HardwareAbstractionLayer hal;
    /**
     * 系统相关信息
     */
    private static Sys sys;
    /**
     * 网络接口名称
     */
    private static String networkName;
    /**
     * 主机IP
     */
    private static String hostIp;
    /**
     * 主机名
     */
    private static String hostName;
    /**
     * Mac地址
     */
    private static String mac;
    /**
     * 操作系统
     */
    private static String osName;
    /**
     * 系统架构/系统位数
     */
    private static String osArch;
    /**
     * 程序运行时的工作目录
     */
    private static String userDir;
    /**
     * JDK版本
     */
    private static String jdkVersion;
    /**
     * JRE路径
     */
    private static String jreHome;
    /**
     * 逻辑CPU数
     */
    private static int logicalCpuCount;
    /**
     * 物理CPU数
     */
    private static int physicalCpuCount;
    /**
     * 上一次系统Cpu负载周期
     */
    private static long[] prevTicks;
    /**
     * 网络接口和统计信息
     */
    private static NetworkIF network;
    /**
     * 获取服务器性能两次执行之间的时间 单位:秒
     */
    private static final int SERVER_RUN_PERIOD = 1;
    /**
     * 执行时间同步两次执行之间的时间 单位:分钟
     */
    private static final int NTPDATE_PERIOD = 5;

    /**
     * 监控中心IP和端口
     */
    private static List<String> centre;

    /**
     * 向监控中心发送监控数据间隔时间(秒)
     */
    private static int intervalTime;

    /**
     * 需要监控的服务名称和心跳地址
     */
    private static Map<String, List<ServerSoftwareStatus>> service;

    /**
     * 需要监控的软件名称和进程名称列表
     */
    private static Map<String, List<ServerSoftwareStatus>> software;

    private static void init() {
        NetworkInterface networkInterface;
        if (networkName != null) {
            networkInterface = HostUtil.getNetworkInterfaceByName(networkName);
        } else {
            networkInterface = HostUtil.getNetworkInterface();
            networkName = HostUtil.getNetworkName(networkInterface);
        }
        hostIp = HostUtil.getHostIp(networkInterface);
        hostName = HostUtil.getHostName();
        Properties props = System.getProperties();
        osName = props.getProperty("os.name");
        osArch = props.getProperty("os.arch");
        userDir = props.getProperty("user.dir");
        jdkVersion = props.getProperty("java.version");
        jreHome = props.getProperty("java.home");
        sys = getSystemInfo();

        systemInfo = new SystemInfo();
        hal = systemInfo.getHardware();
        CentralProcessor processor = hal.getProcessor();
        logicalCpuCount = processor.getLogicalProcessorCount();
        physicalCpuCount = processor.getPhysicalProcessorCount();
        prevTicks = processor.getSystemCpuLoadTicks();
        network = initNetwork(hal.getNetworkIFs());
        if (network == null) {
            throw new RuntimeException("get network interface error.");
        } else {
            mac = network.getMacaddr();
        }
    }

    /**
     * 初始化网络接口和统计信息
     */
    private static NetworkIF initNetwork(List<NetworkIF> networks) {
        for (NetworkIF n : networks) {
            if (networkName.equals(n.getName())) {
                return n;
            }
        }
        return null;
    }

    public MonitorUtil(MonitorConfig config) {
        MonitorUtil.ntpIp = config.getNtpIp();
        String networkName = config.getNetworkName();
        if (networkName != null && !networkName.trim().isEmpty()) {
            MonitorUtil.networkName = networkName;
        }
        init();
        latestServer = getServerRunInfo();
        timingGetServerRunInfo();
        if (ntpIp != null && !ntpIp.isEmpty()) {
            latestNtpdateInfo = getNtpdateInfo();
            timingGetNtpdateInfo();
        } else {
            latestNtpdateInfo = new TimeSyncInfo();
        }
        List<String> centre = config.getCentre();
        if (centre != null && !centre.isEmpty()) {
            MonitorUtil.centre = centre;
            MonitorUtil.intervalTime = config.getIntervalTime();
            MonitorUtil.service = config.getService();
            MonitorUtil.software = config.getSoftware();
            timingPushMonitorData();
        }
    }

    /**
     * 获取最新的服务各项信息
     *
     * @return {@link Server}
     */
    public static Server getLatestServer() {
        return latestServer;
    }

    /**
     * 定时获取服务器运行信息
     */
    private static void timingGetServerRunInfo() {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate(() -> latestServer = getServerRunInfo(), SERVER_RUN_PERIOD, SERVER_RUN_PERIOD, TimeUnit.SECONDS);
    }

    /**
     * 获取最新时间同步信息
     *
     * @return {@link TimeSyncInfo}
     */
    public static TimeSyncInfo getLatestNtpdateInfo() {
        return latestNtpdateInfo;
    }

    /**
     * 定时获取时间同步信息
     */
    private static void timingGetNtpdateInfo() {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate(() -> latestNtpdateInfo = getNtpdateInfo(), NTPDATE_PERIOD, NTPDATE_PERIOD, TimeUnit.MINUTES);
    }

    /**
     * 定时推送监控数据
     */
    private static void timingPushMonitorData() {
        try {
            UdpClient client = UdpClient.getClient();
            ScheduledExecutorService scheduledService = Executors.newScheduledThreadPool(1);
            scheduledService.scheduleAtFixedRate(() -> {
                for (String s : centre) {
                    String[] split = s.split(":");
                    String ip = split[0];
                    int port = Integer.parseInt(split[1]);
                    MonitorData data = new MonitorData();
                    data.setServer(latestServer);
                    if (!service.isEmpty()) {
                        data.setService(getStatusList(service));
                    }
                    if (!software.isEmpty()) {
                        data.setSoftware(getStatusList(software));
                    }
                    client.send(ip, port, JsonTool.toJson(data).getBytes(StandardCharsets.UTF_8));
                }
            }, intervalTime, intervalTime, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取状态列表
     *
     * @param map 需要监控的信息
     * @return {@link List}<{@link ServerSoftwareStatus}>
     */
    private static List<ServerSoftwareStatus> getStatusList(Map<String, List<ServerSoftwareStatus>> map) {
        List<ServerSoftwareStatus> list = new ArrayList<>(map.size());
        map.forEach((k, v) -> v.forEach(l -> {
            l.setName(k);
            String processName = l.getProcessName();
            if (processName == null) {
                l.setProcessName(k);
                processName = k;
            }
            AliveInfo info = CmdUtil.isAlive(processName);
            Integer checkPort = l.getCheckPort();
            boolean result = checkPort == 0 || CmdUtil.isAlive(checkPort);
            if (info.getStatus() && result) {
                l.setWorkDirectory(CmdUtil.getWorkDirectoryByPid(info.getPid()));
                l.setStartupTime(info.getStartupTime());
                l.setStatus(true);
            }
            list.add(l);
        }));
        return list;
    }

    /**
     * 获取同步日期信息
     *
     * @return {@link TimeSyncInfo}
     */
    public static TimeSyncInfo getNtpdateInfo() {
        TimeSyncInfo info;
        try {
            if (osName.contains(CmdUtil.OS_WIN)) {
                info = NtpdateUtil.ntpdate(InetAddress.getByName(ntpIp));
            } else {
                info = getLinuxNtpdateInfo();
            }
            Integer status = info.getStatus();
            if (status == Constants.NTP_SUCCESS) {
                LOG.info("同步时间成功, ntp服务器地址:{}, 偏移秒数:{}", info.getNtpIp(), info.getOffsetSeconds());
            } else if (status == Constants.NTP_UNAVAILABLE) {
                LOG.error("时间同步失败, 原因为: {} 的时间服务器无法连接", ntpIp);
            } else {
                LOG.error("时间同步失败, 原因为: 偏移秒数未获取到, 同步时间原始输出内容为：{}", info.getRawOutputContent());
            }
        } catch (Exception e) {
            LOG.error("同步时间时出错", e);
            info = new TimeSyncInfo();
            info.setNtpIp(ntpIp);
            info.setStatus(Constants.NTP_FAIL);
        }
        return info;
    }

    /**
     * 得到Windows的时间同步信息
     *
     * @return {@link TimeSyncInfo}
     */
    private static TimeSyncInfo getWinNtpdateInfo() {
        //net start w32time

        //w32tm /config /manualpeerlist:"cn.ntp.org.cn" /syncfromflags:manual /reliable:yes /update

        //w32tm /resync

        //w32tm /stripchart /computer:cn.ntp.org.cn /samples:1 /dataonly

        return new TimeSyncInfo();
    }

    /**
     * 得到Linux的ntpdate信息
     *
     * @return {@link TimeSyncInfo}
     */
    private static TimeSyncInfo getLinuxNtpdateInfo() {
        TimeSyncInfo info = new TimeSyncInfo();
        String result = CmdUtil.execStr("ntpdate " + ntpIp);
        info.setNtpIp(ntpIp);
        info.setStatus(Constants.NTP_FAIL);
        if (result != null) {
            if (result.contains(NO_SERVER_SUITABLE_MESSAGE)) {
                // 服务器不可用
                info.setStatus(Constants.NTP_UNAVAILABLE);
            } else {
                Matcher matcher = IP_PATTERN.matcher(result);
                if (matcher.find()) {
                    String ntpIp = matcher.group(1);
                    info.setNtpIp(ntpIp);
                }
                matcher = SEC_PATTERN.matcher(result);
                if (matcher.find()) {
                    String sec = matcher.group(1);
                    info.setOffsetSeconds(Double.parseDouble(sec));
                }
                if (info.getOffsetSeconds() != null) {
                    info.setStatus(Constants.NTP_SUCCESS);
                } else {
                    info.setRawOutputContent(result);
                }
            }
        }
        return info;
    }

    /**
     * 获取服务器运行信息
     *
     * @return 服务器运行信息
     */
    public static Server getServerRunInfo() {
        Server server = new Server();
        server.setCpu(getCpuInfo(hal.getProcessor()));
        server.setMem(getMemoryInfo(hal.getMemory()));
        server.setSysFiles(getDiskInfo(systemInfo.getOperatingSystem().getFileSystem().getFileStores()));
        server.setJvm(getJvmInfo());
        server.setSys(sys);
        server.setNetwork(getNetwork());
        return server;
    }

    /**
     * 获取CPU信息
     *
     * @param processor CentralProcessor对象
     * @return CPU信息
     */
    public static Cpu getCpuInfo(CentralProcessor processor) {
        // CPU信息 CPU利用率 = 1 - (totalIdle-idle1)/(cpu2-cpu1)
        long[] ticks = processor.getSystemCpuLoadTicks();
        long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()];
        long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()];
        long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
        long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()];
        long cSys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
        long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()];
        long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
        long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()];
        long total = user + nice + cSys + iowait + irq + softirq + steal + idle;
        Cpu cpu = new Cpu();
        cpu.setLogicalCpuCount(logicalCpuCount);
        cpu.setPhysicalCpuCount(physicalCpuCount);
        cpu.setSysUsed(Arith.div((double) cSys, (double) total));
        cpu.setUserUsed(Arith.div((double) user, (double) total));
        cpu.setWait(Arith.div((double) iowait, (double) total));
        cpu.setIdle(Arith.div((double) idle, (double) total));
        long totalIdle = idle + iowait;
        cpu.setTotalUsed(total > 0 && totalIdle >= 0 ? (double) (total - totalIdle) / total : 0d);
        prevTicks = ticks;
        return cpu;
    }

    /**
     * 获取内存信息
     *
     * @param memory GlobalMemory对象
     * @return 内存信息
     */
    public static Mem getMemoryInfo(GlobalMemory memory) {
        Mem mem = new Mem();
        long memoryTotal = memory.getTotal();
        long available = memory.getAvailable();
        mem.setTotal(memoryTotal);
        mem.setUsed(memoryTotal - available);
        mem.setFree(available);
        return mem;
    }

    /**
     * 获取磁盘信息
     *
     * @param fsArray 操作系统文件存储信息列表
     * @return 磁盘信息
     */
    public static List<SysFile> getDiskInfo(List<OSFileStore> fsArray) {
        List<SysFile> sysFileList = new ArrayList<>();
        for (OSFileStore fs : fsArray) {
            fs.updateAttributes();
            long free = fs.getFreeSpace();
            long total = fs.getTotalSpace();
            long used = total - free;
            SysFile sysFile = new SysFile();
            sysFile.setDirName(fs.getMount());
            sysFile.setSysTypeName(fs.getType());
            sysFile.setTypeName(fs.getName());
            sysFile.setTotal(total);
            sysFile.setFree(free);
            sysFile.setUsed(used);
            sysFile.setUsage(Arith.div(used, total));
            sysFileList.add(sysFile);
        }
        return sysFileList;
    }

    /**
     * 获取系统信息
     *
     * @return 系统信息
     */
    public static Sys getSystemInfo() {
        Sys sys = new Sys();
        sys.setComputerName(hostName);
        sys.setComputerIp(hostIp);
        sys.setOsName(osName);
        sys.setOsArch(osArch);
        sys.setUserDir(userDir);
        return sys;
    }

    /**
     * 获取JVM信息
     *
     * @return JVM信息
     */
    public static Jvm getJvmInfo() {
        Jvm jvm = new Jvm();
        jvm.setMemoryTotal(Runtime.getRuntime().totalMemory());
        jvm.setMaxAvailableMemory(Runtime.getRuntime().maxMemory());
        jvm.setFreeMemory(Runtime.getRuntime().freeMemory());
        jvm.setUsageMemory(jvm.getMemoryTotal() - jvm.getFreeMemory());
        jvm.setJdkVersion(jdkVersion);
        jvm.setJreHome(jreHome);
        jvm.setStartTime(ManagementFactory.getRuntimeMXBean().getStartTime());
        jvm.setRunTime(System.currentTimeMillis() - jvm.getStartTime());
        return jvm;
    }

    /**
     * 获取网络状态流量信息
     *
     * @return {@link Network}
     */
    public static Network getNetwork() {
        if (network == null) {
            return new Network();
        }
        NetworkIF beforeNetwork = network;
        long rxBytesBegin = beforeNetwork.getBytesRecv();
        long txBytesBegin = beforeNetwork.getBytesSent();
        long rxPacketsBegin = beforeNetwork.getPacketsRecv();
        long txPacketsBegin = beforeNetwork.getPacketsSent();
        network.updateAttributes();
        NetworkIF nowNetwork = network;
        long rxBytesEnd = nowNetwork.getBytesRecv();
        long txBytesEnd = nowNetwork.getBytesSent();
        long rxPacketsEnd = nowNetwork.getPacketsRecv();
        long txPacketsEnd = nowNetwork.getPacketsSent();

        long rxBytesAvg = (rxBytesEnd - rxBytesBegin) / SERVER_RUN_PERIOD;
        long txBytesAvg = (txBytesEnd - txBytesBegin) / SERVER_RUN_PERIOD;
        long rxPacketsAvg = (rxPacketsEnd - rxPacketsBegin) / SERVER_RUN_PERIOD;
        long txPacketsAvg = (txPacketsEnd - txPacketsBegin) / SERVER_RUN_PERIOD;
        Network network = new Network();
        network.setName(networkName);
        network.setMac(mac);
        network.setIp(hostIp);
        network.setRecvBytes(rxBytesAvg);
        network.setSendBytes(txBytesAvg);
        network.setRecvPackets(rxPacketsAvg);
        network.setSendPackets(txPacketsAvg);
        return network;
    }

    /**
     * 字节转换
     *
     * @param size 字节大小
     * @return 转换后值
     */
    public String convertFileSize(long size) {
        long kb = 1024;
        long mb = kb * 1024;
        long gb = mb * 1024;
        if (size >= gb) {
            return String.format("%.1f GB", (float) size / gb);
        } else if (size >= mb) {
            float f = (float) size / mb;
            return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f);
        } else if (size >= kb) {
            float f = (float) size / kb;
            return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f);
        } else {
            return String.format("%d B", size);
        }
    }

}
